In [1]:
%matplotlib inline
import ogr
import shapely
from shapely.geometry import *
import geopandas as gpd
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import IPython.display as display

Enunciado Proyecto

Encargados de planeación en las ciudades esperan medir el impacto que tienen las medidas o proyectos de infraestructura que se ejecutan sobre las ciudades. Pocas veces se cuentan con las herramientas suficientes para medir este impacto. En ese sentido, Uber MOVEMENT le brinda herramientas a las personas involucradas en la toma de decisiones de una ciudad. Uber MOVEMENT provee los tiempos agregados de los viajes realizados entre las diferentes zonas de la ciudad de Bogotá D.C.

En el taller de Pandas, se había formulado la pregunta de cuanto había mejorado la movilidad (luego de la implementación del deprimido de la 94) entre la zona de la AV Cra. 30 entre 26 y Américas (Supercade de la 30), y la zona cercana a Unicentro. Se evidenció que entre el segundo trimestre de 2016 y el segundo trimestre de 2017, hubo una mejora de hasta el 28% de los tiempos de viaje entre el Supercade de la 30 y Unicentro.

En esta ocasión queremos visualizar la mejora de la movilidad no solo hacia Unicentro, sino hacia todos los sectores que probablemente hagan uso del deprimido de la 94 como vía de acceso. Sin embargo, la información agregada de Uber brinda una oportunidad para medir la accesibilidad a puntos de interés como hospitales. Dicha información permite medir qué tan accesible es un hospital en determinada área.

Por otro lado, el objetivo de los tomadores de decisiones de la ciudad es reducir la brecha de desigualdad, generando alternativas serias de movilidad para las personas más vulnerables, brindando las mismas oportunidades a personas con condiciones socioeconómicas tanto favorables como vulnerables. La estratificación clasifica grupos de personas que tienen condiciones sociales y económicas similares Mayor información sobre estratificación. A pesar de que la estratificación se hace netamente con la úbicación del predio, un análisis válido consiste en determinar la accesibilidad de las zonas de estratos bajos con respecto a los estratos altos. El ejercicio final propuesto en este taller, consiste en analizar la composición de estratos por cada localidad y su accesibilidad a través de la comparación de tiempos de viaje hacía los diferentes estratos

Durante este taller se reforzarán las siguientes áreas:

  • Manejo de datos georeferenciados.
  • Creación de nuevas columnas en Pandas.
  • Manejo de Geopandas.
  • Limpieza de información usando OpenStreetMap.
  • Limpieza de archivos y filtrado de información útil en Python.
  • Generación de gráficos en Plotly.

Para la realización de este taller se requiere la instalación de gdal.

conda install gdal

RECOMENDACIONES:

  • Incluya su código sólo donde se solicita. Todas las secciones dónde se pide incluír código, están delimitadas por el título Ejercicio.
  • Desarrolle en orden el taller. Las primeras secciones generan unos DataFrames que son usados posteriormente por las últimas secciones.
  • Use el tutorial de GeoPandas como referencia, así como el taller anterior de Pandas.
  • Have fun!

1 - ¿Cómo impactó el deprimido de la 94 a la movilidad hacia el norte de la ciudad?

En el taller de Pandas observamos la mejora entre dos zonas que probablemente hacen uso del deprimido de la 94 como vía de acceso. En este punto el objetivo es construir un mapa cloroplético que permita conocer en qué porcentaje mejoró o emperó la movilidad entre el segundo bimestre de 2016 y el segundo bimestre de 2017. Recordemos que el deprimido fue inaugurado en Marzo de 2017.

Para este punto se van a usar los siguientes archivos:

  • bogota_cadastral.json: contiene la información georeferenciada de 1160 zonas que componen a la ciudad de Bogotá D.C. (Incluído Sumapaz)
  • bogota-cadastral-2017-2-OnlyWeekdays-HourlyAggregate.csv: contiene la información de viajes entre semana entre un origen y un destino en la ciudad de Bogotá, para el segundo trimestre de 2017. Se encuentra discriminada por la hora en la que se realizó el viaje.
  • bogota-cadastral-2016-2-OnlyWeekdays-HourlyAggregate.csv: contiene la información de viajes entre semana entre un origen y un destino en la ciudad de Bogotá, para el segundo trimestre de 2016. Se encuentra discriminada por la hora en la que se realizó el viaje.

Genere un mapa cloroplético de los tiempos de desplazamiento desde el Supercade de la 30 a las 7 de la noche. El mapa debe contener 10 categorías.

Cargue la información catastral de Bogotá contenida en el archivo data/uber/bogota_cadastral.json. Esta información puede ser cargada con GeoPandas. Por otro lado, cargue la información de viajes entre semana agregados hora a hora para el segundo trimestre de 2016 y para el segundo trimestre de 2017.

In [2]:
import geopandas as gpd

df_bogota = gpd.read_file('/home/dianar/ML/TallerI/bogota_cadastral.json')
df_bogota.MOVEMENT_ID = df_bogota.MOVEMENT_ID.astype(np.int64)

df_2017_2 = pd.read_csv('/home/dianar/ML/TallerI/bogota-cadastral-2017-2-OnlyWeekdays-HourlyAggregate.csv')
df_2016_2 = pd.read_csv('/home/dianar/ML/TallerI/bogota-cadastral-2016-2-OnlyWeekdays-HourlyAggregate.csv')

Ejercicio

Usando .loc filtre aquellos registros de df_bogota que no reporten ningún viaje en df_2017_2. Guarde los datos filtrados en df_bogota

In [3]:
# ESCRIBA SU CÓDIGO AQUÍ
sources=df_2017_2["sourceid"]
df_bogota=df_bogota.loc[df_bogota["MOVEMENT_ID"].isin(sources.unique())]
df_bogota.head()
Out[3]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry
0 12 004575 0 LOS LAURELES 0.027741 0.000037 1 LOS LAURELES, 004575 (1) (POLYGON ((-74.200295 4.617249, -74.2002849999...
2 45 102502 1 EL UVAL RURAL 0.171333 0.000708 3 EL UVAL RURAL, 102502 (3) (POLYGON ((-74.11930700000001 4.490175, -74.11...
3 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09...
4 77 009240 0 POTRERILLO 0.010527 0.000007 5 POTRERILLO, 009240 (5) (POLYGON ((-74.097435 4.735314, -74.096203 4.7...
5 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000...
In [4]:
# PRUEBE SU CÓDIGO AQUÍ
df_bogota.head()
Out[4]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry
0 12 004575 0 LOS LAURELES 0.027741 0.000037 1 LOS LAURELES, 004575 (1) (POLYGON ((-74.200295 4.617249, -74.2002849999...
2 45 102502 1 EL UVAL RURAL 0.171333 0.000708 3 EL UVAL RURAL, 102502 (3) (POLYGON ((-74.11930700000001 4.490175, -74.11...
3 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09...
4 77 009240 0 POTRERILLO 0.010527 0.000007 5 POTRERILLO, 009240 (5) (POLYGON ((-74.097435 4.735314, -74.096203 4.7...
5 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000...
In [5]:
df_2017_2.head()
Out[5]:
sourceid dstid hod mean_travel_time standard_deviation_travel_time geometric_mean_travel_time geometric_standard_deviation_travel_time
0 4 6 0 853.22 344.13 788.49 1.48
1 4 8 6 1793.80 525.03 1721.41 1.33
2 4 9 9 2028.88 438.35 1983.36 1.24
3 5 4 19 1346.33 183.04 1334.16 1.14
4 6 4 10 1336.50 443.70 1275.49 1.34

Como observa en la parte superior. df_2017_2 contiene por cada fila un origen y un destino a determinada hora del día. Para cada uno de estos registros muestra la información del tiempo de viaje promedio, la desviación estándar, la media geométrica del tiempo y la desviación estándar de la media geométrica.

IMPORTANTE:

Cada origen y destino registrado acá corresponde a una zona registrada en el archivo bogota_cadastral.json. La columna que une ambos atributos se llama MOVEMENT_ID

Si deseamos filtrar todos los viajes a las 7pm, lo podemos hacer usando:

In [6]:
df_2017_2.loc[df_2017_2['hod'] == 19].head()
Out[6]:
sourceid dstid hod mean_travel_time standard_deviation_travel_time geometric_mean_travel_time geometric_standard_deviation_travel_time
3 5 4 19 1346.33 183.04 1334.16 1.14
90 6 72 19 2267.32 1560.31 2024.53 1.49
104 6 85 19 2797.11 704.51 2701.83 1.31
105 9 55 19 1157.62 560.88 1066.61 1.47
117 9 68 19 1265.75 359.78 1219.88 1.31

Ejercicio

Defina una función que regrese un dataframe y permita filtrar df_2017_2 o df_2016_2 por un origen definido y una hora definida. En la siguiente celda puede encontrar un ejemplo de como debe ser llamada esta función y qué resultado debe generar. El valor 644 corresponde precisamente a la zona dónde se encuentra el supercade de la 30.

In [7]:
# Construya esta función

def filter_df_by_source_hod(df, sourceid, hod, value='mean_travel_time'):
    # FIltre por hora y sourceid
    tmp_df = df[(df['sourceid'] == sourceid ) & (df['hod'] == hod)]
    # Regrese únicamente los destinos acompañados por su tiempo promedio de viaje  
    tmp_df = tmp_df[['dstid','mean_travel_time']]
    return tmp_df    
In [8]:
df = filter_df_by_source_hod(df_2016_2, 644, 19, value='mean_travel_time')
In [9]:
# Verifique ue la función haya sido definida de forma correcta con este código de prueba
# 

df = filter_df_by_source_hod(df_2016_2, 644, 19, value='mean_travel_time')
df.head()
Out[9]:
dstid mean_travel_time
3429 526 2175.47
4420 658 2655.56
11941 966 1413.65
16833 306 3412.38
23083 504 2667.25

Ejercicio

Como se puede evidenciar, usted acaba de extraer los tiempos promedios a cada zona en la ciudad de Bogotá. Ahora debe hacer una unión de atributos entre el dataframe df y df_bogota. Esto con el objetivo de conocer los nombres y los datos geográficos de cada uno de los destinos que están consignados en df.

In [10]:
df.rename(columns={"dstid": "MOVEMENT_ID", 'mean_travel_time': "time_2016"}, inplace=True)

# PONGA SU CÓDIGO AQUÍ

# Haga un spatial merge entre df_bogota y df, usando la columna MOVEMENT_ID
df_times = df_bogota.merge(df, on='MOVEMENT_ID')

# Cree una nueva columna que represente el tiempo en minutos.
df_times['time_minutes_2016'] = df_times['time_2016'] / 60
In [11]:
# PRUEBE SU CÓDIGO AQUÍ

df_times.head()
Out[11]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry time_2016 time_minutes_2016
0 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09... 1658.77 27.646167
1 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000... 627.56 10.459333
2 183 004104 0 LA ESTANZUELA 0.029466 0.000036 8 LA ESTANZUELA, 004104 (8) (POLYGON ((-74.091623 4.597399, -74.091398 4.5... 869.79 14.496500
3 223 003102 0 LAS NIEVES 0.024801 0.000037 9 LAS NIEVES, 003102 (9) (POLYGON ((-74.073802 4.60692, -74.07320199999... 280.79 4.679833
4 290 003108 0 LA CAPUCHINA 0.021162 0.000024 12 LA CAPUCHINA, 003108 (12) (POLYGON ((-74.078091 4.606167, -74.077551 4.6... 689.60 11.493333

Para propósitos de visualización, vamos a recoger los centroides de las zonas donde se encuentran el deprimido de la 94 (953) y el supercade de la 30 (644)

In [12]:
source_dest_points = df_bogota.loc[df_bogota['MOVEMENT_ID'].isin([644, 953])]
cents = source_dest_points.centroid
cents.head()
Out[12]:
643    POINT (-74.08004747802593 4.623532947145633)
952    POINT (-74.04864053822101 4.676857296680346)
dtype: object

Ejercicio:

Use df_times.plot() para realizar un mapa cloroplético.

In [13]:
f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Tiempo promedio de viaje desde el Supercade de la 30 en el 2016')

##### INGRESE AQUÍ SU CÓDIGO

df_times.plot(column='time_minutes_2016', scheme = 'Fisher_Jenks', k=10, cmap = 'Blues', ax = ax, legend = True, figsize=(15, 10))

# Imprima un mapa cloroplético usando la columna time_minutes_2016 en
# el GeoDataFrame df_times. El mapa debe tener un título, un colormap definido,
# debe estar definido en 10 categorías. 


##### NO MODIFIQUE EL CÓDIGO ABAJO

df_bogota.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)
cents.plot(color='Red', ax=ax)

props = dict(boxstyle='round', facecolor='linen', alpha=1)
for point, txt in zip(cents.iteritems(), ['SUPERCADE', '94']):
    ax.text(point[1].x-0.015,
            point[1].y+0.01,
            txt,
            horizontalalignment='center',
            fontsize=9,
            bbox=props)

ax.set_axis_off()
plt.axis('equal')
Out[13]:
(-74.23244790000003, -73.98948209999999, 4.4353856, 4.856078399999999)
In [13]:
# NO MODIFIQUE ESTA CELDA
Out[13]:
(-74.23244790000003, -73.98948209999999, 4.4353856, 4.856078399999999)

Genere un mapa que visualice en qué porcentaje mejoró la movilidad DESDE el supercade de la 30 entre el segundo trimestre de 2016 y el segundo trimestre de 2017.

Ejercicio

Aplique el mismo flujo de trabajo con df_2016_2, pero esta vez para df_2017_2. Siga las instrucciones a continuación.

In [14]:
value = 'mean_travel_time'

df = filter_df_by_source_hod(df_2017_2, 644, 19, value)
df.rename(columns={"dstid": "MOVEMENT_ID", value: "time_2017"}, inplace=True)


# INGRESE SU CÓDIGO AQUÍ
# Haga una unión de atributos entre df_times y el df que acaba de definir arriba
# Cree la columna time_minutes_2017 que represente el tiempo en minutos

df_times = gpd.GeoDataFrame.merge(df_times, df, on='MOVEMENT_ID')
df_times['time_minutes_2017'] = df_times['time_2017'] / 60

Ejercicio

Ahora, debemos construir una columna que exprese en terminos porcentuales, la mejora de tiempos. Recuerde que si el tiempo a un destino es el mismo entre 2016 y 2017, el valor que debe tomar esta columna es cero.

In [16]:
# INGRESE SU CÓDIGO AQUÍ
# Cree una columna diff que exprese el cambio porcentual en tiempos desde 2016_2 hacía 2017_2
df_times["diff"]=(df_times["time_minutes_2016"]-df_times["time_minutes_2017"])*100/df_times["time_minutes_2017"]
df_times.head()
Out[16]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry time_2016 time_minutes_2016 time_2017 time_minutes_2017 diff
0 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09... 1658.77 27.646167 1615.41 26.923500 2.684148
1 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000... 627.56 10.459333 613.73 10.228833 2.253434
2 183 004104 0 LA ESTANZUELA 0.029466 0.000036 8 LA ESTANZUELA, 004104 (8) (POLYGON ((-74.091623 4.597399, -74.091398 4.5... 869.79 14.496500 841.70 14.028333 3.337294
3 223 003102 0 LAS NIEVES 0.024801 0.000037 9 LAS NIEVES, 003102 (9) (POLYGON ((-74.073802 4.60692, -74.07320199999... 280.79 4.679833 288.26 4.804333 -2.591411
4 290 003108 0 LA CAPUCHINA 0.021162 0.000024 12 LA CAPUCHINA, 003108 (12) (POLYGON ((-74.078091 4.606167, -74.077551 4.6... 689.60 11.493333 609.85 10.164167 13.076986
In [17]:
# PRUEBE SU CÓDIGO AQUÍ

df_times.head()
Out[17]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry time_2016 time_minutes_2016 time_2017 time_minutes_2017 diff
0 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09... 1658.77 27.646167 1615.41 26.923500 2.684148
1 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000... 627.56 10.459333 613.73 10.228833 2.253434
2 183 004104 0 LA ESTANZUELA 0.029466 0.000036 8 LA ESTANZUELA, 004104 (8) (POLYGON ((-74.091623 4.597399, -74.091398 4.5... 869.79 14.496500 841.70 14.028333 3.337294
3 223 003102 0 LAS NIEVES 0.024801 0.000037 9 LAS NIEVES, 003102 (9) (POLYGON ((-74.073802 4.60692, -74.07320199999... 280.79 4.679833 288.26 4.804333 -2.591411
4 290 003108 0 LA CAPUCHINA 0.021162 0.000024 12 LA CAPUCHINA, 003108 (12) (POLYGON ((-74.078091 4.606167, -74.077551 4.6... 689.60 11.493333 609.85 10.164167 13.076986

Ejercicio

Genere el mapa cloroplético usando la columna diff como columna de clasificación. Use el Colormap plt.cm.coolwarm. Puede usar el código del anterior mapa como base.

In [18]:
f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Tiempo promedio de viaje desde el Supercade de la 30 en el 2016')

##### INGRESE AQUÍ SU CÓDIGO

# Imprima un mapa cloroplético usando la columna time_minutes_2016 en
# el GeoDataFrame df_times. El mapa debe tener un título, un colormap definido,
# debe estar definido en 10 categorías. 

df_times.plot(column='diff', cmap= plt.cm.coolwarm, ax = ax, legend = True, figsize=(15,10))

##### NO MODIFIQUE EL CÓDIGO ABAJO

df_bogota.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)
cents.plot(color='Red', ax=ax)

props = dict(boxstyle='round', facecolor='linen', alpha=1)
for point, txt in zip(cents.iteritems(), ['SUPERCADE', '94']):
    ax.text(point[1].x-0.015,
            point[1].y+0.01,
            txt,
            horizontalalignment='center',
            fontsize=9,
            bbox=props)

ax.set_axis_off()
plt.axis('equal')
Out[18]:
(-74.23244790000003, -73.98948209999999, 4.4353856, 4.856078399999999)
In [89]:
# NO MODIFIQUE ESTA CELDA

Tip: Consulte más acerca de los Colormaps de Matplotlib, en caso de que desee usar otro mapa de colores.

2- ¿Qué tan accesibles son los hospitales de la zona de Teusauillo?

In [19]:
osm = """
<iframe width="500" height="400" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" \
src="http://www.openstreetmap.org/export/embed.html?bbox=\
-74.0918%2C4.6214%2C-74.0685%2C4.6484&amp;layer=mapnik" \
style="border: 1px solid black"></iframe><br/><small><a \
href="http://www.openstreetmap.org/#map=17/37.78292/-122.46336">View Larger Map</a></small>
"""
display.HTML(osm)
Out[19]:

OpenStreetMap se alimenta de la información generada por los usuarios para georeferenciar lugares como avenidas, parques, hospitales, cafe, entre otros. OpenStreetMap nos permite descargar un OSM file (Ejemplo), que contiene la información acerca de las vías, los puntos de interés, los caminos y todos los datos georeferenciados de una zona seleccionada. Para este caso se han descargado los puntos de interés de la zona de Teusaquillo. A partir de esta zona, se descargaron todos los datos disponibles por OSM.

In [20]:
driver=ogr.GetDriverByName('OSM')
data = driver.Open('/home/dianar/ML/TallerI/teusaquillo.osm')
layer = data.GetLayer('points')

features=[x for x in layer]
print(len(features))
4926

Existen tan solo en esta zona 4926 puntos georeferenciados. Los puntos de interés se encuentran como amenity, y deben ser extraídos como puntos. La clase Point descrita en el tutorial de GeoPandas, define todo lo necesario para manejarlos como figuras geométricas.

In [21]:
data_list=[]
for feature in features:
    data=feature.ExportToJson(as_object=True) # Se convierte a JSON
    coords=data['geometry']['coordinates'] # Extraemos las coordenadas
    shapely_geo=Point(coords[0],coords[1]) # Usamos shapely para representar las coordenadas
    name=data['properties']['name'] # Extraemos el nombre
    highway=data['properties']['highway'] # Extraemos la dirección 
    other_tags=data['properties']['other_tags']
    if other_tags and 'amenity' in other_tags:
        feat=[x for x in other_tags.split(',') if 'amenity' in x][0] # Se filtran aquellos tags que correspondan a los amenities
        amenity=feat[feat.rfind('>')+2:feat.rfind('"')] # Se extraen los amenities
    else:
        amenity=None
    data_list.append([name, highway, amenity, shapely_geo]) # Data_list contiene en cada fila, un punto de interés

    
# A continuación se crea un GeoDataFrame con las observaciones y el sistema de coordenadas (CRS)
gdf=gpd.GeoDataFrame(data_list, columns=['Name','Highway','Amenity','geometry'], crs={'init': 'epsg:4326'})
In [22]:
gdf.tail()
Out[22]:
Name Highway Amenity geometry
4921 Amen Ramen None restaurant POINT (-74.0622486 4.6398842)
4922 Agencia de Desarrollo Rural None None POINT (-74.0956783 4.6465511)
4923 None traffic_signals None POINT (-74.06984300000001 4.6493373)
4924 Edificio don Sebastián None None POINT (-74.0774528 4.6368973)
4925 None None None POINT (-74.0780324 4.6264496)

Ejercicio

Filtre aquellos puntos de interés que corresponda a hospital

In [23]:
# INGRESE SU CÓDIGO AQUÍ
hospital_df=gdf[gdf["Amenity"]=="hospital"]
hospital_df
Out[23]:
Name Highway Amenity geometry
295 Hospital Militar None hospital POINT (-74.06209440000001 4.6342409)
527 None None hospital POINT (-74.0674004 4.6466656)
800 Salud Total None hospital POINT (-74.0788582 4.6359714)
808 Clínica Nueva None hospital POINT (-74.0707929 4.6343166)
1449 Instituto Laboratorio Clinico None hospital POINT (-74.0670123 4.625976)
2298 Urgencias Clinica Marly None hospital POINT (-74.0654059 4.6369329)
2442 IPS CRC Valoramos None hospital POINT (-74.0662134 4.6292389)
3048 Hospital Universiario Nacional None hospital POINT (-74.0952601 4.6488289)
3184 Centro Odontologíco Fuerza Aérea None hospital POINT (-74.0765505 4.6413289)
In [91]:
# NO MODIFIQUE ESTA CELDA, USELA PARA PROBAR LA CREACIÓN DEL DATAFRAME hospital_df

hospital_df = hospital_df[hospital_df['Name'].notnull()] # Filtra aquellos hospitales con nombre nulo
G = hospital_df["geometry"].apply(lambda geom: geom.wkb) # Filtra aquellos hospitales duplicados por ubicación
hospital_df = hospital_df.loc[G.drop_duplicates().index]
hospital_df
Out[91]:
Name Highway Amenity geometry
295 Hospital Militar None hospital POINT (-74.06209440000001 4.6342409)
800 Salud Total None hospital POINT (-74.0788582 4.6359714)
808 Clínica Nueva None hospital POINT (-74.0707929 4.6343166)
1449 Instituto Laboratorio Clinico None hospital POINT (-74.0670123 4.625976)
2298 Urgencias Clinica Marly None hospital POINT (-74.0654059 4.6369329)
2442 IPS CRC Valoramos None hospital POINT (-74.0662134 4.6292389)
3048 Hospital Universiario Nacional None hospital POINT (-74.0952601 4.6488289)
3184 Centro Odontologíco Fuerza Aérea None hospital POINT (-74.0765505 4.6413289)

A continuación encuentra un mapa que refleje la ubicación de los hospitales obtenidos

In [26]:
#df_bogota_filtered = df_bogota[df_bogota['scanombre'] != 'SUMAPAZ']
In [24]:
fig, ax = plt.subplots(figsize=(10,10))

ax.set_title(u'Hospitales alrededor de la UNAL')
df_bogota.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)
ax.set_axis_off()
plt.axis('equal')

for i,row in hospital_df.iterrows():
    x=row['geometry'].x
    y=row['geometry'].y
    plt.annotate(row['Name'], xy=(x, y), size=7, xytext=(0, 5), textcoords='offset points')
    plt.plot(x, y,'o', color='#f16824')
    ax.set(aspect=1)

Ejercicio

Cada punto que presenta un hospital, debe ser relacionado con el dataframe df_bogota. Recordemos que df_bogota guarda la información correspondiente a las zonas de UBER Movement. Por consiguiente es importante referenciar en qué zona se encuentran los puntos que hayamos usando OpenStreetMap

Use una unión espacial para asociar ambos dataframes (df_bogota y df_hospital) y que genere el resultado mostrado dos celdas abajo.

In [25]:
# INGRESE SU CÓDIGO AQUÍ
hospital_df = gpd.sjoin(hospital_df, df_bogota, how="inner", op='intersects')
In [26]:
# PRUEBE SU CÓDIGO AQUÍ
hospital_df
Out[26]:
Name Highway Amenity geometry index_right cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME
295 Hospital Militar None hospital POINT (-74.06209440000001 4.6342409) 618 327 008208 0 PARDO RUBIO 0.029295 0.000029 619 PARDO RUBIO, 008208 (619)
527 None None hospital POINT (-74.0674004 4.6466656) 28 659 007203 0 CHAPINERO OCCIDENTAL 0.023296 0.000026 29 CHAPINERO OCCIDENTAL, 007203 (29)
800 Salud Total None hospital POINT (-74.0788582 4.6359714) 569 379 007206 0 BELALCAZAR 0.025904 0.000022 570 BELALCAZAR, 007206 (570)
808 Clínica Nueva None hospital POINT (-74.0707929 4.6343166) 176 192 007205 0 PALERMO 0.024980 0.000029 177 PALERMO, 007205 (177)
1449 Instituto Laboratorio Clinico None hospital POINT (-74.0670123 4.625976) 190 248 008101 0 SAGRADO CORAZON 0.017252 0.000016 191 SAGRADO CORAZON, 008101 (191)
2298 Urgencias Clinica Marly None hospital POINT (-74.0654059 4.6369329) 254 1104 008212 0 MARLY 0.023380 0.000028 255 MARLY, 008212 (255)
2442 IPS CRC Valoramos None hospital POINT (-74.0662134 4.6292389) 700 449 008112 0 SUCRE 0.017523 0.000017 701 SUCRE, 008112 (701)
3048 Hospital Universiario Nacional None hospital POINT (-74.0952601 4.6488289) 828 658 005109 0 CENTRO ADMINISTRATIVO OCC. 0.034985 0.000061 829 CENTRO ADMINISTRATIVO OCC., 005109 (829)
3184 Centro Odontologíco Fuerza Aérea None hospital POINT (-74.0765505 4.6413289) 219 478 007207 0 GALERIAS 0.018290 0.000017 220 GALERIAS, 007207 (220)
In [94]:
# NO MODIFIQUE ESTA CELDA

hospital_df
Out[94]:
Name Highway Amenity geometry index_right cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME
295 Hospital Militar None hospital POINT (-74.06209440000001 4.6342409) 618 327 008208 0 PARDO RUBIO 0.029295 0.000029 619 PARDO RUBIO, 008208 (619)
800 Salud Total None hospital POINT (-74.0788582 4.6359714) 569 379 007206 0 BELALCAZAR 0.025904 0.000022 570 BELALCAZAR, 007206 (570)
808 Clínica Nueva None hospital POINT (-74.0707929 4.6343166) 176 192 007205 0 PALERMO 0.024980 0.000029 177 PALERMO, 007205 (177)
1449 Instituto Laboratorio Clinico None hospital POINT (-74.0670123 4.625976) 190 248 008101 0 SAGRADO CORAZON 0.017252 0.000016 191 SAGRADO CORAZON, 008101 (191)
2298 Urgencias Clinica Marly None hospital POINT (-74.0654059 4.6369329) 254 1104 008212 0 MARLY 0.023380 0.000028 255 MARLY, 008212 (255)
2442 IPS CRC Valoramos None hospital POINT (-74.0662134 4.6292389) 700 449 008112 0 SUCRE 0.017523 0.000017 701 SUCRE, 008112 (701)
3048 Hospital Universiario Nacional None hospital POINT (-74.0952601 4.6488289) 828 658 005109 0 CENTRO ADMINISTRATIVO OCC. 0.034985 0.000061 829 CENTRO ADMINISTRATIVO OCC., 005109 (829)
3184 Centro Odontologíco Fuerza Aérea None hospital POINT (-74.0765505 4.6413289) 219 478 007207 0 GALERIAS 0.018290 0.000017 220 GALERIAS, 007207 (220)

MOVEMENT_ID corresponde a la zona de Uber, donde se encuentra el hospital.

Ejercicio

Ahora vamos a filtrar el conjunto de datos de Uber, por las zonas de destino que contienen a los hospitales. Para esto primero filtramos df_2017_2 por las celdas cuya columna dstid se encuentre en las zonas de los hospitales. Posteriormente, agrupamos los tiempos de viaje desde los diferentes orígenes y escogemos como función de agregación la función min().

In [27]:
df_2017_2.head()
Out[27]:
sourceid dstid hod mean_travel_time standard_deviation_travel_time geometric_mean_travel_time geometric_standard_deviation_travel_time
0 4 6 0 853.22 344.13 788.49 1.48
1 4 8 6 1793.80 525.03 1721.41 1.33
2 4 9 9 2028.88 438.35 1983.36 1.24
3 5 4 19 1346.33 183.04 1334.16 1.14
4 6 4 10 1336.50 443.70 1275.49 1.34
In [28]:
# INGRESE SU CÓDIGO ACÁ
sources=hospital_df["MOVEMENT_ID"]
#df_bogota=df_bogota.loc[df_bogota["MOVEMENT_ID"].isin(sources.unique())]
df_2017_hospital_times = df_2017_2[df_2017_2["dstid"].isin(sources.unique())]
df_2017_hospital_times = df_2017_hospital_times.groupby("sourceid").min()
In [29]:
# PRUEBE SU CÓDIGO ACÁ
df_2017_hospital_times.head()
Out[29]:
dstid hod mean_travel_time standard_deviation_travel_time geometric_mean_travel_time geometric_standard_deviation_travel_time
sourceid
1 570 5 2897.58 323.88 2821.83 1.10
3 570 0 1336.29 87.57 1333.46 1.07
4 29 0 571.38 192.95 538.49 1.16
5 29 5 2479.00 483.58 2422.32 1.15
6 29 0 89.56 89.20 66.23 1.32
In [98]:
#NO MODIFIQUE ESTA CELDA

df_2017_hospital_times.head()
Out[98]:
dstid hod mean_travel_time standard_deviation_travel_time geometric_mean_travel_time geometric_standard_deviation_travel_time
sourceid
1 570 5 2897.58 323.88 2821.83 1.10
3 570 0 1336.29 87.57 1333.46 1.07
4 177 0 571.38 192.95 538.49 1.16
5 570 5 2479.00 517.65 2422.32 1.24
6 177 0 89.56 89.20 66.23 1.32

Aplicamos las siguientes operaciones para obtener los tiempos mínimos desde cualquier zona de Bogotá hacía las zonas de los hospitales demarcados.

In [30]:
# En orden hacemos lo siguiente:
# Linea 1 - Convertimos el indice en una columna más
# Linea 2 - Extraemos solo la columna de origen y de tiempo promedio
# Línea 3 - Renombramos las columnas
# Línea 4 - Imprimimos los primeros 5 registros

df_2017_hospital_times.reset_index(inplace=True)
df_2017_hospital_times = df_2017_hospital_times[['sourceid', 'mean_travel_time']]
df_2017_hospital_times.rename(columns={"sourceid": "MOVEMENT_ID", "mean_travel_time": "time"}, inplace=True) 
df_2017_hospital_times.head()
Out[30]:
MOVEMENT_ID time
0 1 2897.58
1 3 1336.29
2 4 571.38
3 5 2479.00
4 6 89.56

Ejercicio

Fusione df_bogota con df_2017_hospital_times en la columna MOVEMENT_ID. Cree una nueva columna que corresponde al tiempo en minutos.

In [31]:
# INGRESE SU CÓDIGO AQUÍ
df_times = pd.merge(df_bogota,df_2017_hospital_times,how = "inner",on= "MOVEMENT_ID")
df_times["time_minutes"] = df_times["time"]/60
In [32]:
# PRUEBE SU CODIGO AQUÍ
df_times.head()
Out[32]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry time time_minutes
0 12 004575 0 LOS LAURELES 0.027741 0.000037 1 LOS LAURELES, 004575 (1) (POLYGON ((-74.200295 4.617249, -74.2002849999... 2897.58 48.293000
1 45 102502 1 EL UVAL RURAL 0.171333 0.000708 3 EL UVAL RURAL, 102502 (3) (POLYGON ((-74.11930700000001 4.490175, -74.11... 1336.29 22.271500
2 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09... 571.38 9.523000
3 77 009240 0 POTRERILLO 0.010527 0.000007 5 POTRERILLO, 009240 (5) (POLYGON ((-74.097435 4.735314, -74.096203 4.7... 2479.00 41.316667
4 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000... 89.56 1.492667
In [102]:
# NO MODIFIQUE ESTA CELDA

df_times.head()
Out[102]:
cartodb_id scacodigo scatipo scanombre shape_leng shape_area MOVEMENT_ID DISPLAY_NAME geometry time time_minutes
0 12 004575 0 LOS LAURELES 0.027741 0.000037 1 LOS LAURELES, 004575 (1) (POLYGON ((-74.200295 4.617249, -74.2002849999... 2897.58 48.293000
1 45 102502 1 EL UVAL RURAL 0.171333 0.000708 3 EL UVAL RURAL, 102502 (3) (POLYGON ((-74.11930700000001 4.490175, -74.11... 1336.29 22.271500
2 74 005501 0 PALO BLANCO 0.017187 0.000018 4 PALO BLANCO, 005501 (4) (POLYGON ((-74.09353299999999 4.681014, -74.09... 571.38 9.523000
3 77 009240 0 POTRERILLO 0.010527 0.000007 5 POTRERILLO, 009240 (5) (POLYGON ((-74.097435 4.735314, -74.096203 4.7... 2479.00 41.316667
4 108 007208 0 BANCO CENTRAL 0.014862 0.000013 6 BANCO CENTRAL, 007208 (6) (POLYGON ((-74.071786 4.642472, -74.0715430000... 89.56 1.492667

Ejercicio

Genere un mapa cloroplético sobre los tiempos de viaje HACÍA los diferentes hospitales de Teusaquillo. Use la columna que recién creo como columna de clasificación.

In [33]:
# INGRESE Y PRUEBE SU CÓDIGO AQUÍ
f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Tiempo promedio de viaje hacia los hospitales de Teusaquillo')

##### INGRESE AQUÍ SU CÓDIGO

# Imprima un mapa cloroplético usando la columna time_minutes_2016 en
# el GeoDataFrame df_times. El mapa debe tener un título, un colormap definido,
# debe estar definido en 10 categorías. 

df_times.plot(column='time_minutes', scheme="Fisher_Jenks",k=10, legend=True, cmap="Blues",figsize=(15,10),ax=ax) # Creamos un mapa coroplético sobre time_minutes_2016

##### NO MODIFIQUE EL CÓDIGO ABAJO

df_bogota.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)
ax.set_axis_off()
plt.axis('equal')

for i,row in hospital_df.iterrows():
    x=row['geometry'].x
    y=row['geometry'].y
    plt.annotate(row['Name'], xy=(x, y), size=7, xytext=(0, 5), textcoords='offset points')
    plt.plot(x, y,'o', color='#f16824')
    ax.set(aspect=1)
In [37]:
# NO MODIFIQUE ESTA CELDA
Out[37]:
(-74.23244790000001, -73.98948209999999, 4.4353856, 4.8560783999999995)

Bonus:

Descargue la información de otra zona de Bogotá y evalue la accesibilidad a otro tipo de zonas como Restaurantes en Modelia.

3- ¿Existe una relación entre la estratificación y las condiciones de movilidad en Bogotá D.C.?

La estratificación permite clasificar a la población en diferentes grupos socioeconómicos. La estratificación define elementos como los subsidios que asigna el gobierno para el cobro de los servicios públicos domiciliarios. Aquellas personas que viven en domicilios de estrato alto pagan más por los servicios públicos, contribuyendo a los hogaros de estrato bajo. Sin embargo, la estratificación también está determinada por las condiciones de acceso y movilidad del domicilio. Durante este ejercicio, vamos a analizar la información de estratos y luego evaluaremos las condiciones de movilidad usando la información provista por UBER Movement.

Vamos a usar los siguientes archivos:

  • bogota-estratos-2013.json: Contiene los polígonos correspondientes a las manzanas de Bogotá D.C.. Contiene información adicional como los estratos y la fecha en que se realizó la estratificación.
  • bogota-localidades.geojson: Contiene la información geográfica de las 20 localidades del Distrito Capital.
  • bogota_cadastral.json: contiene la información georeferenciada de 1160 zonas que componen a la ciudad de Bogotá D.C. (Incluído Sumapaz)
  • bogota-cadastral-2017-2-OnlyWeekdays-HourlyAggregate.csv: contiene la información de viajes entre semana entre un origen y un destino en la ciudad de Bogotá, para el segundo trimestre de 2017. Se encuentra discriminada por la hora en la que se realizó el viaje.

Este ejercicio supone un reto adicional, puesto que el JSON que contiene la información, no viene en un formato de carga directa a GeoPandas. Primero cargue el JSON y visualice la información que contiene.

In [34]:
df = pd.read_json('/home/dianar/ML/TallerI/bogota_estratos_2013.json')
In [35]:
df.head()
Out[35]:
datasetid fields geometry record_timestamp recordid
0 estratificacion-socioeconomica-2013@bogota-lab... {'shape_area': 61354.848985305005, 'globalid':... {'type': 'Point', 'coordinates': [-74.10514036... 2017-10-28T00:09:39-05:00 5d519128f1f380096221d62aac227441ed97f694
1 estratificacion-socioeconomica-2013@bogota-lab... {'shape_area': 595.4008366749999, 'globalid': ... {'type': 'Point', 'coordinates': [-74.10213945... 2017-10-28T00:09:39-05:00 b8a735877e5b4d73da693b16be75bf2e47d981c9
2 estratificacion-socioeconomica-2013@bogota-lab... {'shape_area': 1800.647863675, 'globalid': '{5... {'type': 'Point', 'coordinates': [-74.10722535... 2017-10-28T00:09:39-05:00 fc6258591bf18c5c9db08101be635e84a581b964
3 estratificacion-socioeconomica-2013@bogota-lab... {'shape_area': 1137.075717185, 'globalid': '{5... {'type': 'Point', 'coordinates': [-74.10388184... 2017-10-28T00:09:39-05:00 41f77989dbadd7bcf2083d6781c30e91a3bb1b4a
4 estratificacion-socioeconomica-2013@bogota-lab... {'shape_area': 2207.0964316, 'globalid': '{5B4... {'type': 'Point', 'coordinates': [-74.10635801... 2017-10-28T00:09:39-05:00 e5e953aabf9df5bb1607216a619c59111e65dd26
In [36]:
df['fields'][1]
Out[36]:
{'codigo_criterio': '394',
 'codigo_manzana': '00260549',
 'codigo_zonaest': 19,
 'estrato': 0,
 'fecha_acto': '2017-07-27T21:00:00-05:00',
 'fecha_captura': '2017-07-27T21:00:00-05:00',
 'geo_point_2d': [4.5100035667319265, -74.102139454267],
 'geo_shape': {'coordinates': [[[-74.10205090927423, 4.510120330233975],
    [-74.10203515197718, 4.510031416582815],
    [-74.10202769577712, 4.509986854924595],
    [-74.10202030983895, 4.509942221858372],
    [-74.10201278248763, 4.509897588783347],
    [-74.1021207997112, 4.50989222774242],
    [-74.10222874668095, 4.509886796172888],
    [-74.102236202888, 4.50993142924147],
    [-74.10224358884352, 4.509975991797412],
    [-74.10225104595735, 4.510020554357579],
    [-74.10226588812947, 4.510109679473147],
    [-74.10221198488296, 4.510112430520912],
    [-74.10215801138801, 4.5101150396399525],
    [-74.1021030533857, 4.510117861123295],
    [-74.10205090927423, 4.510120330233975]]],
  'type': 'Polygon'},
 'globalid': '{5B4680E7-A753-52AD-E053-480E080AA7DB}',
 'normativa': 'DEC394',
 'numero_acto': 394,
 'objectid': 43460,
 'responsable': '560',
 'shape_area': 595.4008366749999,
 'shape_len': 97.9038195015579}

Para definir un GeoDataFrame, tenga en cuenta que cada fila debe tener mínimo una columna que se llame geometry y un sistema de coordenadas de referencia.

Ejercicio

Complete la siguiente función para construir el GeoDataFrame. Use lo aprendido en el taller de GeoPandas

In [39]:
from geopandas import GeoDataFrame
from shapely.geometry import Polygon, MultiPolygon

my_keys=["objectid","codigo_manzana","shape_area","shape_len","estrato"]
geometry = []
values = []

# MODIFIQUE LAS LÍNEAS QUE SE SOLICITAN

for idx, row_value in df.iterrows():
    if row_value.fields.get('geo_shape', 0) == 0:
        continue
    
    if row_value.fields['geo_shape']['type'] == 'Polygon':
        geometry.append( Polygon([[x,y] for x,y in row_value.fields["geo_shape"]["coordinates"][0]])
                         )
    else:
        multipo = []
        for poly in row_value.fields['geo_shape']['coordinates'][0]:
            multipo.append(Polygon([[x, y] for x, y in poly]))
        geometry.append(MultiPolygon(multipo))
    
    values.append([df["fields"][idx][x] for x in my_keys]
    )
In [40]:
# PRUEBE SE CÓDIGO AQUÍ

df = pd.DataFrame(values, columns=['id', 'manzana', 'shape_area', 'shape_len', 'estrato'])
crs = {'init': 'epsg:4326'}
estratos = GeoDataFrame(df, crs=crs, geometry=geometry)
estratos.head()
Out[40]:
id manzana shape_area shape_len estrato geometry
0 43421 00260601 61354.848985 1990.056871 2 POLYGON ((-74.10284641458252 4.512802572112657...
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975...
2 43453 00252714 1800.647864 169.204418 2 POLYGON ((-74.10705935714046 4.510386791510661...
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334...
4 43486 00250606 2207.096432 232.198962 2 POLYGON ((-74.10636192782435 4.509094172763362...
In [42]:
# NO MODIFIQUE ESTA CELDA

df = pd.DataFrame(values, columns=['id', 'manzana', 'shape_area', 'shape_len', 'estrato'])
crs = {'init': 'epsg:4326'}
estratos = GeoDataFrame(df, crs=crs, geometry=geometry)
estratos.head()
Out[42]:
id manzana shape_area shape_len estrato geometry
0 43421 00260601 61354.848985 1990.056871 2 POLYGON ((-74.10284641458252 4.512802572112657...
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975...
2 43453 00252714 1800.647864 169.204418 2 POLYGON ((-74.10705935714046 4.510386791510661...
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334...
4 43486 00250606 2207.096432 232.198962 2 POLYGON ((-74.10636192782435 4.509094172763362...

Como puede evidenciar, hemos asociado la información de los estratos con las manzanas respectivas en el archivo json.

In [41]:
f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Estratos en Bogotá')

estratos.plot(column='estrato', categorical=True, legend=True, ax=ax)

ax.set_axis_off()
plt.axis('equal')
Out[41]:
(-74.23423799487468, -73.99558441153367, 4.448018991479677, 4.851263309298175)

Información localidades

Cargue la información de las localidades. Conserve todas las localidades menos Sumapaz.

In [42]:
df_localidades = gpd.read_file('/home/dianar/ML/TallerI/bogota_localidades.geojson')
df_localidades = df_localidades.loc[df_localidades['CODIGO_LOC'] != '20'] # Filtrar Sumapaz
In [43]:
df_localidades.head()
Out[43]:
OBJECTID NOMBRE CODIGO_LOC DECRETO LINK SIMBOLO ESCALA_CAP FECHA_CAPT SHAPE_AREA SHAPE_LEN geometry
0 1 SANTA FE 3 Acuerdo 117 de 2003 4.517065e+07 43779.905440 POLYGON ((-74.06856265714661 4.627917706598259...
1 11 PUENTE ARANDA 16 Acuerdo 8 de 1977 1.731115e+07 17854.555403 POLYGON ((-74.11829863986144 4.637411585408281...
2 13 CIUDAD BOLIVAR 19 Acuerdo 14 de 1983 1.299864e+08 77732.027669 POLYGON ((-74.15216009957933 4.599758614797869...
3 6 BARRIOS UNIDOS 12 Acuerdo 8 de 1977 1.190345e+07 13426.542795 POLYGON ((-74.05724703178382 4.686836295669596...
4 2 SUBA 11 Acuerdo 8 de 1977 1.005606e+08 65665.349126 POLYGON ((-74.04245386600061 4.830660965527887...
In [44]:
df_localidades.loc[7,'geometry']
Out[44]:
In [45]:
localidades = df_localidades[['CODIGO_LOC', 'geometry', 'SHAPE_AREA']]
localidades.rename(index=str, columns={"SHAPE_AREA": "shape_area_loc"}, inplace=True)
/home/dianar/anaconda3/lib/python3.6/site-packages/pandas/core/frame.py:3027: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  return super(DataFrame, self).rename(**kwargs)

Ejercicio

Haga un spatial join, entre los estratos y las localidades.

In [47]:
# INGRESE SU CÓDIGO AQUÍ
estratos_localidades = gpd.sjoin(estratos,localidades, how="inner", op='within')
In [48]:
# PRUEBE SU CÓDIGO AQUÍ
#Index_right copia el valor del indice del dataframe de la derecha, este valor no lo necesitamos

estratos_localidades.drop('index_right', axis=1, inplace=True) 
estratos_localidades.head()
Out[48]:
id manzana shape_area shape_len estrato geometry CODIGO_LOC shape_area_loc
0 43421 00260601 61354.848985 1990.056871 2 POLYGON ((-74.10284641458252 4.512802572112657... 5 2.150664e+08
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975... 5 2.150664e+08
2 43453 00252714 1800.647864 169.204418 2 POLYGON ((-74.10705935714046 4.510386791510661... 5 2.150664e+08
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334... 5 2.150664e+08
4 43486 00250606 2207.096432 232.198962 2 POLYGON ((-74.10636192782435 4.509094172763362... 5 2.150664e+08
In [49]:
# NO MODIFIQUE ESTA CELDA

estratos_localidades.drop('index_right', axis=1, inplace=True)
estratos_localidades.head()
Out[49]:
id manzana shape_area shape_len estrato geometry CODIGO_LOC shape_area_loc
0 43421 00260601 61354.848985 1990.056871 2 POLYGON ((-74.10284641458252 4.512802572112657... 5 2.150664e+08
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975... 5 2.150664e+08
2 43453 00252714 1800.647864 169.204418 2 POLYGON ((-74.10705935714046 4.510386791510661... 5 2.150664e+08
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334... 5 2.150664e+08
4 43486 00250606 2207.096432 232.198962 2 POLYGON ((-74.10636192782435 4.509094172763362... 5 2.150664e+08

Ejercicio

Visualice solo las manzanas que pertenecen a Kennedy en el mapa de Bogotá.

In [49]:
# INGRESE SU CÓDIGO AQUÍ
#df_kennedy = estratos_localidades.loc[estratos_localidades['CODIGO_LOC'] == '8']
#df_kennedy.head()

#df_kennedy.plot(column='manzana', categorical=True, legend=True, ax=ax)
#df_2017_2 = pd.read_csv('data/uber/2017-2/bogota-cadastral-2017-2-OnlyWeekdays-HourlyAggregate.csv')
#df_bogota = gpd.read_file('/home/dianar/ML/TallerI/bogota_cadastral.json')
#df_bogota.MOVEMENT_ID = df_bogota.MOVEMENT_ID.astype(np.int64)
#df_bogota = df_bogota.loc[df_bogota['MOVEMENT_ID'].isin(df_2017_2['sourceid'].unique())]
#df_bogota_filtered = df_bogota['geometry']

df_kennedy = estratos_localidades.loc[estratos_localidades['CODIGO_LOC'] == '8']
df_kennedy.plot(column='manzana', categorical=True, legend=True, ax=ax)
df_2017_2 = pd.read_csv('/home/dianar/ML/TallerI/bogota-cadastral-2017-2-OnlyWeekdays-HourlyAggregate.csv')
df_bogota = gpd.read_file('/home/dianar/ML/TallerI/bogota_cadastral.json')
df_bogota.MOVEMENT_ID = df_bogota.MOVEMENT_ID.astype(np.int64)
df_bogota = df_bogota.loc[df_bogota['MOVEMENT_ID'].isin(df_2017_2['sourceid'].unique())]
df_bogota_filtered = df_bogota['geometry']
<matplotlib.figure.Figure at 0x7fa46f34beb8>
In [50]:
# PRUEBE AQUÍ SU CÓDIGO
f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Localidad de Kennedy')

df_kennedy.plot(legend=True, ax=ax) # k nos permite controlar el número de clases para el mapa

df_bogota_filtered.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)

ax.set_axis_off()
plt.axis('equal')
Out[50]:
(-74.23244790000001, -73.98948209999999, 4.4353856, 4.8560783999999995)
In [51]:
# NO MODIFIQUE ESTA CELDA

f, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title(u'Localidad de Kennedy')

df_kennedy.plot(legend=True, ax=ax) # k nos permite controlar el número de clases para el mapa

df_bogota_filtered.plot(color='None', edgecolor='black', linewidth=0.1, ax=ax)

ax.set_axis_off()
plt.axis('equal')
Out[51]:
(-74.23244790000001, -73.98948209999999, 4.4353856, 4.8560783999999995)

Distribución de estratos por localidad

A continuación encontramos que cada polígono tiene asociado un área. Vamos a hallar qué estrato tiene más porcentaje de área en Kennedy.

In [51]:
estratos_localidades.head()
Out[51]:
id manzana shape_area shape_len estrato geometry CODIGO_LOC shape_area_loc
0 43421 00260601 61354.848985 1990.056871 2 POLYGON ((-74.10284641458252 4.512802572112657... 5 2.150664e+08
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975... 5 2.150664e+08
2 43453 00252714 1800.647864 169.204418 2 POLYGON ((-74.10705935714046 4.510386791510661... 5 2.150664e+08
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334... 5 2.150664e+08
4 43486 00250606 2207.096432 232.198962 2 POLYGON ((-74.10636192782435 4.509094172763362... 5 2.150664e+08

Ejercicio

Vamos a generar el total de área por localidad y estrato. Para esto se debe generar un groupby de pandas usando las columnas CODIGO_LOC y estrato. Recuerde llamar .reset_index() para que la localidad y el estrato sigan siendo columnas del nuevo dataframe.

In [52]:
# INGRESE SU CÓDIGO AQUÍ
areas_groupings=estratos_localidades.groupby(['CODIGO_LOC','estrato'])[['shape_area']].sum().reset_index()
In [53]:
# PRUEBE AQUÍ SU CÓDIGO

areas_groupings.head()
Out[53]:
CODIGO_LOC estrato shape_area
0 1 0 7.658636e+06
1 1 1 7.703923e+05
2 1 2 1.232852e+06
3 1 3 3.893668e+06
4 1 4 4.454413e+06
In [54]:
# NO MODIFIQUE ESTA CELDA

areas_groupings.head()
Out[54]:
CODIGO_LOC estrato shape_area
0 1 0 7.658636e+06
1 1 1 7.703923e+05
2 1 2 1.232852e+06
3 1 3 3.893668e+06
4 1 4 4.454413e+06

Ejercicio

Añada una columna adicional que contenga el área total de cada localidad.

In [54]:
# INGRESE SU CÓDIGO AQUÍ
#area_estratos_localidad = pd.merge(areas_groupings,df_localidades, how="inner", on="CODIGO_LOC")
area_total=df_localidades.groupby('CODIGO_LOC').sum().reset_index()
In [55]:
area_estratos_localidad=pd.merge(areas_groupings,area_total, how='inner', on='CODIGO_LOC')
area_estratos_localidad.rename(columns={'SHAPE_AREA': 'shape_area_loc'}, inplace=True)
area_estratos_localidad.drop(['OBJECTID','SHAPE_LEN'], axis=1, inplace=True)
In [56]:
# PRUEBE AQUÍ SU CÓDIGO

area_estratos_localidad.head()
Out[56]:
CODIGO_LOC estrato shape_area shape_area_loc
0 1 0 7.658636e+06 6.531573e+07
1 1 1 7.703923e+05 6.531573e+07
2 1 2 1.232852e+06 6.531573e+07
3 1 3 3.893668e+06 6.531573e+07
4 1 4 4.454413e+06 6.531573e+07
In [51]:
# NO MODIFIQUE ESTA CELDA

area_estratos_localidad.head()
Out[51]:
CODIGO_LOC estrato shape_area shape_area_loc
0 1 0 7.658636e+06 6.531573e+07
1 1 1 7.703923e+05 6.531573e+07
2 1 2 1.232852e+06 6.531573e+07
3 1 3 3.893668e+06 6.531573e+07
4 1 4 4.454413e+06 6.531573e+07
In [57]:
area_estratos_localidad['ratio'] = area_estratos_localidad['shape_area']/area_estratos_localidad['shape_area_loc']

Ejercicio

Usando plotly, visualice el porcentaje de área por estrato en la localidad de Kennedy

In [58]:
from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

import cufflinks as cf

init_notebook_mode(connected=True)
cf.go_offline()

strata = area_estratos_localidad.loc[area_estratos_localidad['CODIGO_LOC'] == '8', 'estrato']

ratio = area_estratos_localidad.loc[area_estratos_localidad['CODIGO_LOC'] == '8', 'ratio']
In [59]:
pldfes = pd.DataFrame({'Estrato':strata,'Porcentaje':ratio})
pldfes.iplot(kind = 'bar', x = 'Estrato', y = 'Porcentaje', title = 'Distribución de área en Kennedy',xTitle = 'Estrato', 
         yTitle = 'Área',dimensions= (700,450), theme = 'white',color='rgb(255,205,210)')

NO MODIFIQUE ESTA CELDA

Integremos la información de UBER Movement con la información del estrato y localidad.

Hasta el momento, hemos hecho un análisis descriptivo de las localidades y los estratos en la ciudad de Bogotá D.C.. Sin embargo, aún no hemos relacionado esta información con los datos de tiempos de viajes entre zonas de Bogotá. Al analizar esta situación, nos encontramos con un problema:

  • La granularidad de los datos es diferente. Mientras que la información de estratos está a nivel de manzanas, la información de movilidad de Uber se encuentra a nivel de conjuntos de barrios (Similar a una UPZ). De la misma forma pasa entre la información de Uber y la información de las localidades

Para anotar las zonas de uber, usaremos una función de agregación espacial, conocida como geopandas.dissolve. Sin embargo, antes debemos combinar el dataframe estratos_localidades con la información de Uber (df_bogota). A continuación, podemos observar que esto lo logramos usando gpd.sjoin. Observamos que al final del nuevo DataFrame, existe una columna MOVEMENT_ID que vincula la manzana con su respectiva zona de Uber.

In [60]:
df_bogota_uber = df_bogota[['MOVEMENT_ID', 'geometry']] #Versión reducida de df_bogota
# Hacemos unión espacial preguntando si una manzana se encuentra contenida en una determinada zona de Uber
df_bogota_uber = gpd.sjoin(estratos_localidades, df_bogota_uber, how='inner', op='within') 
# Eliminamos index_right
df_bogota_uber.drop('index_right', axis=1, inplace=True)
In [61]:
df_bogota_uber.head()
Out[61]:
id manzana shape_area shape_len estrato geometry CODIGO_LOC shape_area_loc MOVEMENT_ID
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975... 5 2.150664e+08 1137
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334... 5 2.150664e+08 1137
10 43482 00260524 1933.053801 210.514007 1 POLYGON ((-74.10174963243327 4.509400892724955... 5 2.150664e+08 1137
13 43463 00260522 7148.041184 1088.189007 1 POLYGON ((-74.10079449144534 4.509713897783399... 5 2.150664e+08 1137
20 43474 00260529 1057.700962 137.622975 1 POLYGON ((-74.10304584061723 4.509265026320508... 5 2.150664e+08 1137

Ejercicio

Genere un nuevo DataFrame df_bogota_uber_agg, que a través de la función geopandas.dissolve agregue las manzanas que corresponden a una zona de Uber. La función de agregación que usaremos consiste en el estrato con mayor frecuencia dentro de esa zona de uber.

La función de agregación que regresa el elemento de mayor frecuencia en un grupo es:

lambda x:x.value_counts().index[0]

Enlace StackOverflow

In [100]:
#df_bogota_uber.apply(lambda x: x.value_counts().index[0])
df_bogota_uber.head(15)
Out[100]:
id manzana shape_area shape_len estrato geometry CODIGO_LOC shape_area_loc MOVEMENT_ID
1 43460 00260549 595.400837 97.903820 0 POLYGON ((-74.10205090927423 4.510120330233975... 5 2.150664e+08 1137
3 43470 00260532 1137.075717 163.131125 1 POLYGON ((-74.10373634567594 4.509743808347334... 5 2.150664e+08 1137
10 43482 00260524 1933.053801 210.514007 1 POLYGON ((-74.10174963243327 4.509400892724955... 5 2.150664e+08 1137
13 43463 00260522 7148.041184 1088.189007 1 POLYGON ((-74.10079449144534 4.509713897783399... 5 2.150664e+08 1137
20 43474 00260529 1057.700962 137.622975 1 POLYGON ((-74.10304584061723 4.509265026320508... 5 2.150664e+08 1137
332 24909 00260514 737.110442 111.107257 1 POLYGON ((-74.10043093004397 4.509135910677506... 5 2.150664e+08 1137
348 24916 00260543 855.827652 120.012950 1 POLYGON ((-74.09676512381193 4.50907224987123,... 5 2.150664e+08 1137
12533 24873 00260521 1153.539233 144.299051 1 POLYGON ((-74.10053074920485 4.509695589638914... 5 2.150664e+08 1137
12534 24892 00260510 1440.720996 168.111248 1 POLYGON ((-74.0987775142399 4.509470830766192,... 5 2.150664e+08 1137
12541 24844 00260554 959.879348 128.007092 1 POLYGON ((-74.10064322554219 4.510398348652083... 5 2.150664e+08 1137
12542 24845 00260553 935.016469 126.620809 1 POLYGON ((-74.1008665064993 4.510402035504959,... 5 2.150664e+08 1137
12543 24847 00260555 839.362415 117.999975 1 POLYGON ((-74.10048426190777 4.510380118524759... 5 2.150664e+08 1137
12545 24851 00260552 931.288139 131.901816 1 POLYGON ((-74.10108550052378 4.510324930439487... 5 2.150664e+08 1137
12547 24885 00260523 2254.182885 189.910208 1 POLYGON ((-74.09991770026281 4.509574929896241... 5 2.150664e+08 1137
12548 24893 00260511 1440.077602 168.300793 1 POLYGON ((-74.09849625305908 4.50941099590464,... 5 2.150664e+08 1137
In [62]:
# INGRESE SU CÓDIGO AQUÍ
df_bogota_uber_agg = df_bogota_uber.dissolve(by='MOVEMENT_ID',aggfunc=lambda x: x.value_counts().index[0])
In [63]:
# PRUEBE SU CÓDIGO AQUÍ

df_bogota_uber_agg.reset_index(inplace=True)
df_bogota_uber_agg.head()
Out[63]:
MOVEMENT_ID geometry id manzana shape_area shape_len estrato CODIGO_LOC shape_area_loc
0 1 (POLYGON ((-74.20310748630153 4.60964265266627... 14398 00457505 4735.226401 401.969377 2 7 2.393545e+07
1 3 POLYGON ((-74.11029613210192 4.486242755191304... 26062 10250202 3901.914944 269.090069 1 5 2.150664e+08
2 4 (POLYGON ((-74.0950641374056 4.682848705834439... 6443 00550103 2383.619627 237.658937 3 10 3.588097e+07
3 5 (POLYGON ((-74.09599438285443 4.73515747514524... 1852 00924084 1770.577218 206.676242 3 11 1.005606e+08
4 6 (POLYGON ((-74.07065929814955 4.64068645062739... 9756 00720804 3326.522755 290.595315 4 13 1.419317e+07
In [66]:
# NO MODIFIQUE ESTA CELDA

df_bogota_uber_agg.reset_index(inplace=True)
df_bogota_uber_agg.head()
Out[66]:
MOVEMENT_ID geometry id manzana shape_area shape_len estrato CODIGO_LOC shape_area_loc
0 1 (POLYGON ((-74.20310748630153 4.60964265266627... 14398 004575Y9 4735.226401 401.969377 2 7 2.393545e+07
1 3 POLYGON ((-74.11029613210192 4.486242755191304... 26062 10250202 3901.914944 269.090069 1 5 2.150664e+08
2 4 (POLYGON ((-74.0950641374056 4.682848705834439... 6443 00550117 2383.619627 237.658937 3 10 3.588097e+07
3 5 (POLYGON ((-74.09599438285443 4.73515747514524... 1852 00924090 1770.577218 206.676242 3 11 1.005606e+08
4 6 (POLYGON ((-74.07065929814955 4.64068645062739... 9756 00720816 3326.522755 290.595315 4 13 1.419317e+07
In [64]:
df_bogota_uber_agg.count()
Out[64]:
MOVEMENT_ID       948
geometry          948
id                948
manzana           948
shape_area        948
shape_len         948
estrato           948
CODIGO_LOC        948
shape_area_loc    948
dtype: int64

El resultado final son 948 zonas de Uber anotadas con estrato y localidad

Ahora queremos combinar esta información con los datos de viaje. Crearemos cuatro columnas adicionales en df_2017_2. Vamos a añadir el estrato de la zona origen, el estrato de la zona destino, la localidad de la zona origen y la localidad de la zona destino.

In [65]:
# Fusionamos df_2017_2 con df_bogota_uber_agg.
# Añadimos la columna source_estrato y source_loc

df_all = pd.merge(df_2017_2[['sourceid', 'dstid', 'hod', 'mean_travel_time']], 
                  df_bogota_uber_agg[['MOVEMENT_ID', 'estrato', 'CODIGO_LOC']], 
                  left_on = 'sourceid', 
                  right_on = 'MOVEMENT_ID')
df_all = df_all.drop(columns=['MOVEMENT_ID'])
df_all.rename(columns={'estrato': 'source_estrato', 'CODIGO_LOC': 'source_loc'}, inplace=True)

# Fusionamos df_2017_2 con df_bogota_uber_agg.
# Añadimos la columna dst_estrato y dst_loc

df_all = pd.merge(df_all, 
                  df_bogota_uber_agg[['MOVEMENT_ID', 'estrato', 'CODIGO_LOC']], 
                  left_on = 'dstid', 
                  right_on = 'MOVEMENT_ID')
df_all = df_all.drop(columns=['MOVEMENT_ID'])
df_all.rename(columns={'estrato': 'dst_estrato', 'CODIGO_LOC': 'dst_loc'}, inplace=True)
In [66]:
df_all.head()
Out[66]:
sourceid dstid hod mean_travel_time source_estrato source_loc dst_estrato dst_loc
0 4 6 0 853.22 3 10 4 13
1 4 6 1 876.30 3 10 4 13
2 4 6 2 613.33 3 10 4 13
3 4 6 5 950.24 3 10 4 13
4 4 6 6 1281.92 3 10 4 13

Ejercicio

Teniendo los estratos origen y destino, podemos formularnos preguntas como:

  • ¿Qué tan comunes son los viajes de estrato 6 a cualquier estrato en horas pico?
  • ¿Cuánto tiempo toma en promedio un viaje entre estrato 6 y cualquier estrato en horas pico?
In [67]:
# ESCRIBA SU CÓDIGO AQUÍ
hpico = [7,8,9,17,18,19]
# Filtre df_all por las horas pico (7, 8, 9, 17, 18, 19)
df_all = df_all[df_all["hod"].isin(hpico)]
# Filtre el anterior dataframe también por source_estrato igual a 6
df_all = df_all[df_all["source_estrato"]==6]
# Genere df_from_e6_mean agrupando los valores por el estrato destino y use mean() como función de agregación
df_from_e6_mean = df_all.groupby("dst_estrato").mean().reset_index()
# Genere df_from_e6_count agrupando los valores por el estrato destino y use count() como función de agregación
df_from_e6_count = df_all.groupby("dst_estrato").count().reset_index()
In [68]:
# PRUEBE SU CÓDIGO AQUÍ

print(df_from_e6_mean['mean_travel_time'])
print('---------------')
print(df_from_e6_count['sourceid'])
0    2098.474012
1    1605.665908
2    2380.233225
3    2091.056049
4    1719.709806
5    1294.455041
6    1235.372797
Name: mean_travel_time, dtype: float64
---------------
0     3906
1     1349
2    10301
3    29815
4    11729
5     4838
6     4362
Name: sourceid, dtype: int64
In [54]:
# NO MODIFIQUE LA SIGUIENTE CELDA

print(df_from_e6_mean['mean_travel_time'])
print('---------------')
print(df_from_e6_count['sourceid'])
0    2099.393470
1    1605.665908
2    2380.233225
3    2091.978598
4    1719.709806
5    1294.455041
6    1235.372797
Name: mean_travel_time, dtype: float64
---------------
0     3908
1     1349
2    10301
3    29934
4    11729
5     4838
6     4362
Name: sourceid, dtype: int64

Ejercicio

Genere el diagrama de líneas para la frecuencia de viajes y para el tiempo promedio entre estratos en Plot.ly

In [70]:
# INGRESE SU CÓDIGO AQUÍ
df_from_e6_count.iplot(x="dst_estrato",y = "mean_travel_time", xTitle="Estratos",yTitle="# de Viajes", title =" Frecuencia de viajes desde estrato 6",
dimensions= (700,450))

NO MODIFIQUE ESTA CELDA

In [71]:
# INGRESE SU CÓDIGO AQUÍ
df_from_e6_mean.iplot(x="dst_estrato",y="mean_travel_time",title="Frecuencia de viajes desde estrato 6",yTitle="# de Viajes",
                      xTitle="Estratos",dimensions= (700,450))

NO MODIFIQUE ESTA CELDA:

Bonus:

Cree una gráfica de Plot.ly que permita visualizar la distribución de estratos de todas las localidades. (Parecido al ejercicio de porcentaje de triples por posición y por equipos en el taller de la NBA)